package com.zh.utils;

import cn.hutool.core.collection.CollUtil;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.*;
import org.springframework.stereotype.Component;

import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;
import java.util.stream.Collectors;

/**
 * Redis工具类
 * 有些方法他们可能现在没有用，但在曾经或者将来会有用
 */
@Component
@RequiredArgsConstructor
@Slf4j
public class RedisUtil {

    private static final String LOG_PREFIX = "[RedisUtil.{}] [error] {}";
    private static final String KEY_NULL_MESSAGE = "Key cannot be null or empty";
    private static final int DEFAULT_SCAN_COUNT = 1000;
    private static final int DEFAULT_EXPIRE_DAYS = 31;
    private static final int DEFAULT_LIST_EXPIRE_DAYS = 7;
    private static final String SCAN_PATTERN_SUFFIX = "*";

    private final RedisTemplate<String, ?> redisTemplate;
    private final HashOperations<String, String, Object> hashOps;
    private final SetOperations<String, String> setOps;
    private final ZSetOperations<String, String> zSetOps;
    private final ObjectMapper objectMapper;

    /**
     * 校验key
     */
    private void validateKey(String key) {
        if (key == null || key.trim().isEmpty()) {
            throw new IllegalArgumentException(KEY_NULL_MESSAGE);
        }
    }

    /**
     * 统一异常处理
     */
    private <T> T executeWithErrorHandling(String methodName, String key, Supplier<T> action) {
        try {
            return action.get();
        } catch (Exception e) {
            log.error(LOG_PREFIX, methodName, String.format("Failed for key: %s, error: %s", key, e.getMessage()));
            return null;
        }
    }

    /**
     * 无返回值的异常处理
     */
    private void executeVoid(String methodName, String key, Runnable action) {
        try {
            action.run();
        } catch (Exception e) {
            log.error(LOG_PREFIX, methodName, String.format("Failed for key: %s, error: %s", key, e.getMessage()));
        }
    }

    public boolean set(String key, Object value) {
        validateKey(key);
        return this.set(key, value, DEFAULT_EXPIRE_DAYS, TimeUnit.DAYS);
    }

    @SuppressWarnings("unchecked")
    public <T> boolean set(String key, T value, long timeout, TimeUnit timeUnit) {
        validateKey(key);
        return Boolean.TRUE.equals(executeWithErrorHandling("set", key, () -> {
            ValueOperations<String, T> ops = (ValueOperations<String, T>) redisTemplate.opsForValue();
            ops.set(key, value, timeout, timeUnit);
            return true;
        }));
    }

    @SuppressWarnings("unchecked")
    public <T> T get(String key) {
        if (key == null || key.trim().isEmpty()) {
            return null;
        }
        return executeWithErrorHandling("get", key, () -> {
            ValueOperations<String, T> ops = (ValueOperations<String, T>) redisTemplate.opsForValue();
            return ops.get(key);
        });
    }

    public Integer increment(String key, Integer n) {
        validateKey(key);
        return executeWithErrorHandling("increment", key, () ->
                Math.toIntExact(redisTemplate.opsForValue().increment(key, n))
        );
    }

    public void addSet(String key, String... values) {
        validateKey(key);
        executeVoid("addSet", key, () -> setOps.add(key, values));
    }

    public void removeSet(String key, Object... values) {
        validateKey(key);
        executeVoid("removeSet", key, () -> setOps.remove(key, values));
    }

    public Set<String> getSet(String key) {
        validateKey(key);
        return executeWithErrorHandling("getSet", key, () -> setOps.members(key));
    }

    public Long getSetSize(String key) {
        validateKey(key);
        return executeWithErrorHandling("getSetSize", key, () -> setOps.size(key));
    }

    public Boolean isInSet(String key, String value) {
        validateKey(key);
        return executeWithErrorHandling("isInSet", key, () -> setOps.isMember(key, value));
    }

    public void addZSet(String key, Map<String, Double> members) {
        validateKey(key);
        executeVoid("addZSet", key, () -> {
            members.forEach((member, score) -> zSetOps.add(key, member, score));
        });
    }

    public void addZSet(String key, String member, Double score) {
        validateKey(key);
        executeVoid("addZSet", key, () -> zSetOps.add(key, member, score));
    }

    public void removeZSet(String key, String... values) {
        validateKey(key);
        executeVoid("removeZSet", key, () -> zSetOps.remove(key, (Object) values));
    }

    public void removeZSet(String key, int start, int end) {
        validateKey(key);
        executeVoid("removeZSet", key, () -> zSetOps.removeRange(key, start, end));
    }

    public Set<String> getZSet(String key) {
        validateKey(key);
        return executeWithErrorHandling("getZSet", key, () -> zSetOps.range(key, 0, -1));
    }

    public Set<String> getZSet(String key, int start, int end) {
        validateKey(key);
        return executeWithErrorHandling("getZSet", key, () -> zSetOps.range(key, start, end));
    }

    public Set<String> getZSet(String key, int number,boolean reverse) {
        validateKey(key);
        if(reverse){

        return executeWithErrorHandling("getZSet", key, () -> zSetOps.reverseRange(key, 0, number));
        } else {
            return executeWithErrorHandling("getZSet", key, () -> zSetOps.range(key, 0, number));
        }
    }

    public Set<String> rangeZSet(String key, long start, long end) {
        validateKey(key);
        return executeWithErrorHandling("rangeZSet", key, () -> zSetOps.range(key, start, end));
    }

    public Boolean isInZSet(String key, String value) {
        validateKey(key);
        return executeWithErrorHandling("isInZSet", key, () -> zSetOps.score(key, value) != null);
    }

    public Long getZSetSize(String key) {
        validateKey(key);
        return executeWithErrorHandling("getZSetSize", key, () -> zSetOps.zCard(key));
    }

    @SuppressWarnings("unchecked")
    public <T> List<T> getObjList(List<String> keys, boolean ascending) {
        if (CollUtil.isEmpty(keys)) {
            return Collections.emptyList();
        }

        return executeWithErrorHandling("getObjList", keys.toString(), () -> {
            ValueOperations<String, T> ops = (ValueOperations<String, T>) redisTemplate.opsForValue();
            List<T> values = ops.multiGet(keys);

            if (CollUtil.isEmpty(values)) {
                return Collections.emptyList();
            }

            return sortList(values, ascending);
        });
    }

    private <T> List<T> sortList(List<T> list, boolean ascending) {
        list.sort((o1, o2) -> {
            if (o1 == null && o2 == null) return 0;
            if (o1 == null) return ascending ? -1 : 1;
            if (o2 == null) return ascending ? 1 : -1;
            int comparison = ((Comparable<T>) o1).compareTo((T) o2);
            return ascending ? comparison : -comparison;
        });
        return list;
    }

    public void delete(String key) {
        if (key == null || key.trim().isEmpty()) {
            return;
        }
        executeVoid("delete", key, () -> redisTemplate.delete(key));
    }

    public void deleteKeys(List<String> keys) {
        if (CollUtil.isEmpty(keys)) {
            return;
        }
        executeVoid("deleteKeys", keys.toString(), () -> redisTemplate.delete(keys));
    }

    public void deleteByPrefix(String prefix) {
        validateKey(prefix);
        executeVoid("deleteByPrefix", prefix, () -> {
            Set<String> keysToDelete = scanKeys(prefix);
            if (!keysToDelete.isEmpty()) {
                redisTemplate.delete(keysToDelete);
                log.info("Deleted {} keys with prefix: {}", keysToDelete.size(), prefix);
            }
        });
    }

    private Set<String> scanKeys(String prefix) {
        Set<String> keys = new HashSet<>();
        try (Cursor<byte[]> cursor = Objects.requireNonNull(redisTemplate.getConnectionFactory())
                .getConnection()
                .scan(ScanOptions.scanOptions()
                        .match(prefix + SCAN_PATTERN_SUFFIX)
                        .count(DEFAULT_SCAN_COUNT)
                        .build())) {
            while (cursor.hasNext()) {
                keys.add(new String(cursor.next(), StandardCharsets.UTF_8));
            }
        }
        return keys;
    }

    public <T> Map<String, T> getKeyValuesByPrefix(String prefix) {
        validateKey(prefix);
        List<String> keys = getKeysByPrefix(prefix, true);
        if (keys.isEmpty()) {
            return Collections.emptyMap();
        }

        Map<String, T> keyValues = new HashMap<>();
        for (String key : keys) {
            T value = get(key);
            if (value != null) {
                keyValues.put(key, value);
            }
        }
        return keyValues;
    }

    public List<String> getKeysByPrefix(String prefix, boolean ascending) {
        validateKey(prefix);
        List<String> keys = new ArrayList<>(scanKeys(prefix));
        if (ascending) {
            Collections.sort(keys);
        } else {
            keys.sort(Collections.reverseOrder());
        }
        return keys;
    }

    public void setHash(String key, Map<String, ?> fields, long timeout, TimeUnit timeUnit) {
        validateKey(key);
        executeVoid("setHash", key, () -> {
            hashOps.putAll(key, fields);
            redisTemplate.expire(key, timeout, timeUnit);
        });
    }

    @SuppressWarnings("unchecked")
    public <V> Map<String, V> getHashEntries(String key) {
        validateKey(key);
        return executeWithErrorHandling("getHashEntries", key, () ->
                (Map<String, V>) hashOps.entries(key));
    }

    @SuppressWarnings("unchecked")
    public <T> void setList(String key, List<T> list, long timeout, TimeUnit timeUnit) {
        validateKey(key);
        if (CollUtil.isEmpty(list)) {
            log.warn("List is empty for key: {}", key);
            return;
        }

        executeVoid("setList", key, () -> {
            List<String> jsonList = serializeList(list);
            if (!jsonList.isEmpty()) {
                ListOperations<String, String> listOps = (ListOperations<String, String>) redisTemplate.opsForList();
                listOps.rightPushAll(key, jsonList);
                redisTemplate.expire(key, timeout, timeUnit);
            }
        });
    }

    private <T> List<String> serializeList(List<T> list) {
        return list.stream()
                .map(item -> {
                    try {
                        return objectMapper.writeValueAsString(item);
                    } catch (JsonProcessingException e) {
                        log.error("JSON serialization failed: {}", e.getMessage());
                        return null;
                    }
                })
                .filter(Objects::nonNull)
                .collect(Collectors.toList());
    }

    public <T> void setList(String key, List<T> list) {
        setList(key, list, DEFAULT_LIST_EXPIRE_DAYS, TimeUnit.DAYS);
    }

    @SuppressWarnings("unchecked")
    public <T> List<T> getList(String key, Class<T> clazz) {
        validateKey(key);
        return executeWithErrorHandling("getList", key, () -> {
            List<String> jsonList = (List<String>) redisTemplate.opsForList().range(key, 0, -1);
            if (CollUtil.isEmpty(jsonList)) {
                return null;
            }

            return jsonList.stream()
                    .map(json -> deserializeJson(json, clazz))
                    .filter(Objects::nonNull)
                    .collect(Collectors.toList());
        });
    }

    private <T> T deserializeJson(String json, Class<T> clazz) {
        try {
            return objectMapper.readValue(json, clazz);
        } catch (Exception e) {
            log.error("JSON deserialization failed: {}", e.getMessage());
            return null;
        }
    }

    public Boolean hasKey(String key) {
        validateKey(key);
        return executeWithErrorHandling("hasKey", key, () -> redisTemplate.hasKey(key));
    }

    public long getExpire(String key) {
        validateKey(key);
        return executeWithErrorHandling("getExpire", key, () ->
                Optional.ofNullable(redisTemplate.getExpire(key, TimeUnit.SECONDS))
                        .orElse(-1L));
    }

    @SuppressWarnings("unchecked")
    public <T> void setEx(String key, T o, long expire, TimeUnit timeUnit) {
        validateKey(key);
        executeVoid("setEx", key, () -> {
            ValueOperations<String, T> ops = (ValueOperations<String, T>) redisTemplate.opsForValue();
            ops.setIfAbsent(key, o, expire, timeUnit);
        });
    }

    public Boolean exists(String key) {
        Boolean exists = executeWithErrorHandling("exists", key, () -> redisTemplate.hasKey(key));
        return exists;
    }
}
